﻿//-----------------------------------------------------------------------
// <copyright>
// This file is subject to the terms and conditions of the Microsoft Public License (MS-PL).
// See http://www.microsoft.com/opensource/licenses.mspx#Ms-PL for more details.
// </copyright>
//----------------------------------------------------------------------- 
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;

namespace Fulcrum
{
    public class CommandLineProcessor
    {
        Regex splitRegEx;
        List<ISettings> settingsObjects;
        Dictionary<string, CommandLineSetting> cmdLineSettings;
        FulcrumSettings fulcrumSettings;
        FulcrumGenerationUnitManager unitManager;
        string applicationName;


        public CommandLineProcessor(string applicationName, FulcrumSettings fulcrumSettings, FulcrumGenerationUnitManager unitManager)
        {
            this.applicationName = applicationName;
            this.fulcrumSettings = fulcrumSettings;
            this.unitManager = unitManager;

            this.settingsObjects = new List<ISettings>();
            this.cmdLineSettings = new Dictionary<string, CommandLineSetting>(StringComparer.OrdinalIgnoreCase);
            this.splitRegEx = new Regex(@"^-(?<Command>\w+)(:(?<Value>.+))?$");

            this.AddSettingsObject(fulcrumSettings);
            this.fulcrumSettings.ModuleChanged += new EventHandler(OnFulcrumSettingsModuleChanged);
        }

        void OnFulcrumSettingsModuleChanged(object sender, EventArgs e)
        {
            var genUnit = this.unitManager.GetGenerationUnit(this.fulcrumSettings.Module);
            var genUnitSettings = genUnit.Settings;

            AddSettingsObject(genUnitSettings);
        }

        public void AddSettingsObject(ISettings settings)
        {
            ProcessSettingsObject(settings);
        }

        void ProcessSettingsObject(ISettings settings)
        {
            this.settingsObjects.Add(settings);

            var type = settings.GetType();

            var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);

            foreach (var property in properties)
            {
                var settingsAttribs = property.GetCustomAttributes(typeof(SettingAttribute), true);

                if (settingsAttribs.Length > 1)
                {
                    throw new ApplicationException(String.Format("The property {0} has more than one SettingsAttribute specified.", property.Name));
                }

                if (settingsAttribs.Length > 0)
                {
                    var settingsAttrib = (SettingAttribute)settingsAttribs[0];

                    ProcessSettingsProperty(property, settings, settingsAttrib);
                }
            }
        }

        void ProcessSettingsProperty(PropertyInfo property, ISettings settings, SettingAttribute settingsAttrib)
        {
            var cmdLineSetting = new CommandLineSetting(property, settings, settingsAttrib);

            this.cmdLineSettings.Add(cmdLineSetting.Name, cmdLineSetting);
        }

        public void ParseCommandLine(string[] args)
        {
            IDictionary<string, string> arguments = TokenizeArguments(args);

            foreach (var entry in arguments)
            {
                var command = entry.Key;
                CommandLineSetting setting;

                if (false == this.cmdLineSettings.TryGetValue(command, out setting))
                {
                    throw new ArgumentException(String.Format("Command '{0}' not recognized.", command));
                }

                setting.SetValue(entry.Value);
            }

            foreach (var commandLineSetting in this.cmdLineSettings.Values)
            {
                if (false == commandLineSetting.Optional &&
                    false == commandLineSetting.HasValue)
                {
                    throw new ArgumentException(String.Format("Command '{0}' is required to have a value.", commandLineSetting.Name));
                }
            }

            foreach (var settingObject in this.settingsObjects)
            {
                settingObject.ValidateSettings();
            }
        }

        public void ShowUsage()
        {
            Console.WriteLine("{0} [options]", this.applicationName);

            foreach (var settings in this.cmdLineSettings.Values)
            {
                string optional = settings.Optional ? "  (Optional)" : String.Empty;
                Console.WriteLine("\t-{0} - {1}{2}", settings.Name, settings.Description, optional);
            }
        }

        private IDictionary<string, string> TokenizeArguments(string[] args)
        {
            Dictionary<string, string> arguments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

            foreach (string arg in args)
            {
                string value;
                string command;

                SplitArgument(arg, out command, out value);

                arguments.Add(command, value);
            }
            return arguments;
        }

        void SplitArgument(string arg, out string command, out string value)
        {
            Match match = this.splitRegEx.Match(arg);

            if (match.Success)
            {
                command = match.Groups["Command"].Value;
                value = match.Groups["Value"].Value;
            }
            else
            {
                throw new ApplicationException(String.Format("The command '{0}' is has an invalid format.", arg));
            }
        }
    }
}
